
// See the file "COPYING" in the main distribution directory for copyright.

#include "zeek/Func.h"

#include "zeek/zeek-config.h"

#include <broker/error.hh>
#include <netdb.h>
#include <netinet/in.h>
#include <sys/param.h>
#include <unistd.h>
#include <algorithm>
#include <cctype>
#include <cerrno>
#include <csignal>
#include <cstdlib>

#include "zeek/Base64.h"
#include "zeek/Debug.h"
#include "zeek/Desc.h"
#include "zeek/Event.h"
#include "zeek/EventTrace.h"
#include "zeek/Expr.h"
#include "zeek/File.h"
#include "zeek/Frame.h"
#include "zeek/NetVar.h"
#include "zeek/RE.h"
#include "zeek/Reporter.h"
#include "zeek/RunState.h"
#include "zeek/Scope.h"
#include "zeek/ScriptProfile.h"
#include "zeek/Stmt.h"
#include "zeek/Traverse.h"
#include "zeek/Var.h"
#include "zeek/analyzer/protocol/tcp/TCP.h"
#include "zeek/iosource/PktDumper.h"
#include "zeek/iosource/PktSrc.h"
#include "zeek/module_util.h"
#include "zeek/plugin/Manager.h"
#include "zeek/session/Manager.h"

// Ignore clang-format's reordering of include files here so that it doesn't
// break what symbols are available when, which keeps the build from breaking.
// clang-format off
#include "zeek.bif.func_h"
#include "communityid.bif.func_h"
#include "stats.bif.func_h"
#include "reporter.bif.func_h"
#include "strings.bif.func_h"
#include "option.bif.func_h"
#include "supervisor.bif.func_h"
#include "packet_analysis.bif.func_h"
#include "CPP-load.bif.func_h"

#include "zeek.bif.func_def"
#include "communityid.bif.func_def"
#include "stats.bif.func_def"
#include "reporter.bif.func_def"
#include "strings.bif.func_def"
#include "option.bif.func_def"
#include "supervisor.bif.func_def"
#include "packet_analysis.bif.func_def"
#include "CPP-load.bif.func_def"
// clang-format on

extern RETSIGTYPE sig_handler(int signo);

namespace zeek::detail
	{
std::vector<CallInfo> call_stack;
bool did_builtin_init = false;
std::vector<void (*)()> bif_initializers;
static const std::pair<bool, zeek::ValPtr> empty_hook_result(false, nullptr);
	} // namespace zeek::detail

namespace zeek
	{

std::string render_call_stack()
	{
	std::string rval;
	int lvl = 0;

	if ( ! detail::call_stack.empty() )
		rval += "| ";

	for ( auto it = detail::call_stack.rbegin(); it != detail::call_stack.rend(); ++it )
		{
		if ( lvl > 0 )
			rval += " | ";

		auto& ci = *it;
		auto name = ci.func->Name();
		std::string arg_desc;

		for ( const auto& arg : ci.args )
			{
			ODesc d;
			d.SetShort();
			arg->Describe(&d);

			if ( ! arg_desc.empty() )
				arg_desc += ", ";

			arg_desc += d.Description();
			}

		rval += util::fmt("#%d %s(%s)", lvl, name, arg_desc.data());

		if ( ci.call )
			{
			auto loc = ci.call->GetLocationInfo();
			rval += util::fmt(" at %s:%d", loc->filename, loc->first_line);
			}

		++lvl;
		}

	if ( ! detail::call_stack.empty() )
		rval += " |";

	return rval;
	}

void Func::AddBody(const detail::FunctionIngredients& ingr, detail::StmtPtr new_body)
	{
	if ( ! new_body )
		new_body = ingr.Body();

	AddBody(new_body, ingr.Inits(), ingr.FrameSize(), ingr.Priority(), ingr.Groups());
	}

void Func::AddBody(detail::StmtPtr new_body, const std::vector<detail::IDPtr>& new_inits,
                   size_t new_frame_size, int priority)
	{
	std::set<EventGroupPtr> groups;
	AddBody(new_body, new_inits, new_frame_size, priority, groups);
	}

void Func::AddBody(detail::StmtPtr new_body, size_t new_frame_size)
	{
	std::vector<detail::IDPtr> no_inits;
	std::set<EventGroupPtr> no_groups;
	AddBody(new_body, no_inits, new_frame_size, 0, no_groups);
	}

void Func::AddBody(detail::StmtPtr /* new_body */,
                   const std::vector<detail::IDPtr>& /* new_inits */, size_t /* new_frame_size */,
                   int /* priority */, const std::set<EventGroupPtr>& /* groups */)
	{
	Internal("Func::AddBody called");
	}

void Func::SetScope(detail::ScopePtr newscope)
	{
	scope = std::move(newscope);
	}

FuncPtr Func::DoClone()
	{
	// By default, ok just to return a reference. Func does not have any state
	// that is different across instances.
	return {NewRef{}, this};
	}

void Func::DescribeDebug(ODesc* d, const Args* args) const
	{
	d->Add(Name());

	if ( args )
		{
		d->Add("(");
		const auto& func_args = GetType()->Params();
		auto num_fields = static_cast<size_t>(func_args->NumFields());

		for ( auto i = 0u; i < args->size(); ++i )
			{
			// Handle varargs case (more args than formals).
			if ( i >= num_fields )
				{
				d->Add("vararg");
				int va_num = i - num_fields;
				d->Add(va_num);
				}
			else
				d->Add(func_args->FieldName(i));

			d->Add(" = '");
			(*args)[i]->Describe(d);

			if ( i < args->size() - 1 )
				d->Add("', ");
			else
				d->Add("'");
			}

		d->Add(")");
		}
	}

detail::TraversalCode Func::Traverse(detail::TraversalCallback* cb) const
	{
	// FIXME: Make a fake scope for builtins?
	auto old_scope = cb->current_scope;
	cb->current_scope = scope;

	detail::TraversalCode tc = cb->PreFunction(this);
	HANDLE_TC_STMT_PRE(tc);

	// FIXME: Traverse arguments to builtin functions, too.
	if ( kind == SCRIPT_FUNC && scope )
		{
		tc = scope->Traverse(cb);
		HANDLE_TC_STMT_PRE(tc);

		for ( const auto& body : bodies )
			{
			tc = body.stmts->Traverse(cb);
			HANDLE_TC_STMT_PRE(tc);
			}
		}

	tc = cb->PostFunction(this);

	cb->current_scope = old_scope;
	HANDLE_TC_STMT_POST(tc);
	}

void Func::CopyStateInto(Func* other) const
	{
	other->bodies = bodies;
	other->scope = scope;
	other->kind = kind;

	other->type = type;

	other->name = name;
	}

void Func::CheckPluginResult(bool handled, const ValPtr& hook_result, FunctionFlavor flavor) const
	{
	// Helper function factoring out this code from ScriptFunc:Call() for
	// better readability.

	if ( ! handled )
		{
		if ( hook_result )
			reporter->InternalError(
				"plugin set processed flag to false but actually returned a value");

		// The plugin result hasn't been processed yet (read: fall
		// into ::Call method).
		return;
		}

	switch ( flavor )
		{
		case FUNC_FLAVOR_EVENT:
			if ( hook_result )
				reporter->InternalError("plugin returned non-void result for event %s",
				                        this->Name());

			break;

		case FUNC_FLAVOR_HOOK:
			if ( hook_result->GetType()->Tag() != TYPE_BOOL )
				reporter->InternalError("plugin returned non-bool for hook %s", this->Name());

			break;

		case FUNC_FLAVOR_FUNCTION:
			{
			const auto& yt = GetType()->Yield();

			if ( (! yt) || yt->Tag() == TYPE_VOID )
				{
				if ( hook_result )
					reporter->InternalError("plugin returned non-void result for void method %s",
					                        this->Name());
				}

			else if ( hook_result && hook_result->GetType()->Tag() != yt->Tag() &&
			          yt->Tag() != TYPE_ANY )
				{
				reporter->InternalError("plugin returned wrong type (got %d, expecting %d) for %s",
				                        hook_result->GetType()->Tag(), yt->Tag(), this->Name());
				}

			break;
			}
		}
	}

namespace detail
	{

ScriptFunc::ScriptFunc(const IDPtr& arg_id) : Func(SCRIPT_FUNC)
	{
	name = arg_id->Name();
	type = arg_id->GetType<zeek::FuncType>();
	frame_size = 0;
	}

ScriptFunc::ScriptFunc(std::string _name, FuncTypePtr ft, std::vector<StmtPtr> bs,
                       std::vector<int> priorities)
	{
	name = std::move(_name);
	frame_size = ft->ParamList()->GetTypes().size();
	type = std::move(ft);

	auto n = bs.size();
	ASSERT(n == priorities.size());

	for ( auto i = 0u; i < n; ++i )
		{
		Body b;
		b.stmts = std::move(bs[i]);
		b.priority = priorities[i];
		bodies.push_back(b);
		}

	std::stable_sort(bodies.begin(), bodies.end());

	if ( ! bodies.empty() )
		{
		current_body = bodies[0].stmts;
		current_priority = bodies[0].priority;
		}
	}

ScriptFunc::~ScriptFunc()
	{
	if ( captures_vec )
		{
		auto& cvec = *captures_vec;
		auto& captures = *type->GetCaptures();
		for ( auto i = 0u; i < captures.size(); ++i )
			if ( captures[i].IsManaged() )
				ZVal::DeleteManagedType(cvec[i]);
		}

	delete captures_frame;
	delete captures_offset_mapping;
	}

bool ScriptFunc::IsPure() const
	{
	return std::all_of(bodies.begin(), bodies.end(),
	                   [](const Body& b)
	                   {
						   return b.stmts->IsPure();
					   });
	}

ValPtr ScriptFunc::Invoke(zeek::Args* args, Frame* parent) const
	{
	SegmentProfiler prof(segment_logger, location);

	if ( sample_logger )
		sample_logger->FunctionSeen(this);

	auto [handled, hook_result] = PLUGIN_HOOK_WITH_RESULT(
		HOOK_CALL_FUNCTION, HookCallFunction(this, parent, args), empty_hook_result);

	CheckPluginResult(handled, hook_result, Flavor());

	if ( handled )
		return hook_result;

	if ( bodies.empty() )
		{
		// Can only happen for events and hooks.
		assert(Flavor() == FUNC_FLAVOR_EVENT || Flavor() == FUNC_FLAVOR_HOOK);
		return Flavor() == FUNC_FLAVOR_HOOK ? val_mgr->True() : nullptr;
		}

	auto f = make_intrusive<Frame>(frame_size, this, args);

	// Hand down any trigger.
	if ( parent )
		{
		f->SetTrigger({NewRef{}, parent->GetTrigger()});
		f->SetTriggerAssoc(parent->GetTriggerAssoc());
		}

	g_frame_stack.push_back(f.get()); // used for backtracing
	const CallExpr* call_expr = parent ? parent->GetCall() : nullptr;
	call_stack.emplace_back(CallInfo{call_expr, this, *args});

	// If a script function is ever invoked with more arguments than it has
	// parameters log an error and return. Most likely a "variadic function"
	// that only has a single any parameter and is excluded from static type
	// checking is involved. This should otherwise not be possible to hit.
	auto num_params = static_cast<size_t>(GetType()->Params()->NumFields());
	if ( args->size() > num_params )
		{
		emit_builtin_exception("too many arguments for function call");
		return nullptr;
		}

	if ( etm && Flavor() == FUNC_FLAVOR_EVENT )
		etm->StartEvent(this, args);

	if ( g_trace_state.DoTrace() )
		{
		ODesc d;
		DescribeDebug(&d, args);

		g_trace_state.LogTrace("%s called: %s\n", GetType()->FlavorString().c_str(),
		                       d.Description());
		}

	StmtFlowType flow = FLOW_NEXT;
	ValPtr result;

	for ( const auto& body : bodies )
		{
		if ( body.disabled )
			continue;

		if ( sample_logger )
			sample_logger->LocationSeen(body.stmts->GetLocationInfo());

		// Fill in the rest of the frame with the function's arguments.
		for ( auto j = 0u; j < args->size(); ++j )
			{
			const auto& arg = (*args)[j];

			if ( f->GetElement(j) != arg )
				// Either not yet set, or somebody reassigned the frame slot.
				f->SetElement(j, arg);
			}

		if ( spm )
			spm->StartInvocation(this, body.stmts);

		f->Reset(args->size());

		try
			{
			result = body.stmts->Exec(f.get(), flow);
			}

		catch ( InterpreterException& e )
			{
			// Already reported, but now determine whether to unwind further.
			if ( Flavor() == FUNC_FLAVOR_FUNCTION )
				{
				g_frame_stack.pop_back();
				call_stack.pop_back();
				// Result not set b/c exception was thrown
				throw;
				}

			// Continue exec'ing remaining bodies of hooks/events.
			continue;
			}

		if ( spm )
			spm->EndInvocation();

		if ( f->HasDelayed() )
			{
			assert(! result);
			assert(parent);
			parent->SetDelayed();
			break;
			}

		if ( Flavor() == FUNC_FLAVOR_HOOK )
			{
			// Ignore any return values of hook bodies, final return value
			// depends on whether a body returns as a result of break statement.
			result = nullptr;

			if ( flow == FLOW_BREAK )
				{
				// Short-circuit execution of remaining hook handler bodies.
				result = val_mgr->False();
				break;
				}
			}
		}

	call_stack.pop_back();

	if ( Flavor() == FUNC_FLAVOR_HOOK )
		{
		if ( ! result )
			result = val_mgr->True();
		}

	else if ( etm && Flavor() == FUNC_FLAVOR_EVENT )
		etm->EndEvent(this, args);

	// Warn if the function returns something, but we returned from
	// the function without an explicit return, or without a value.
	else if ( GetType()->Yield() && GetType()->Yield()->Tag() != TYPE_VOID &&
	          (flow != FLOW_RETURN /* we fell off the end */ ||
	           ! result /* explicit return with no result */) &&
	          ! f->HasDelayed() )
		reporter->Warning("non-void function returning without a value: %s", Name());

	if ( result && g_trace_state.DoTrace() )
		{
		ODesc d;
		result->Describe(&d);

		g_trace_state.LogTrace("Function return: %s\n", d.Description());
		}

	g_frame_stack.pop_back();

	return result;
	}

void ScriptFunc::CreateCaptures(Frame* f)
	{
	const auto& captures = type->GetCaptures();

	if ( ! captures )
		return;

	// Create *either* a private Frame to hold the values of captured
	// variables, and a mapping from those variables to their offsets
	// in the Frame; *or* a ZVal frame if this script has a ZAM-compiled
	// body.
	ASSERT(bodies.size() == 1);

	if ( bodies[0].stmts->Tag() == STMT_ZAM )
		captures_vec = std::make_unique<std::vector<ZVal>>();
	else
		{
		delete captures_frame;
		delete captures_offset_mapping;
		captures_frame = new Frame(captures->size(), this, nullptr);
		captures_offset_mapping = new OffsetMap;
		}

	int offset = 0;
	for ( const auto& c : *captures )
		{
		auto v = f->GetElementByID(c.Id());

		if ( v )
			{
			if ( c.IsDeepCopy() || ! v->Modifiable() )
				v = v->Clone();

			if ( captures_vec )
				// Don't use v->GetType() here, as that might
				// be "any" and we need to convert.
				captures_vec->push_back(ZVal(v, c.Id()->GetType()));
			else
				captures_frame->SetElement(offset, std::move(v));
			}

		else if ( captures_vec )
			captures_vec->push_back(ZVal());

		if ( ! captures_vec )
			captures_offset_mapping->insert_or_assign(c.Id()->Name(), offset);

		++offset;
		}
	}

void ScriptFunc::CreateCaptures(std::unique_ptr<std::vector<ZVal>> cvec)
	{
	const auto& captures = *type->GetCaptures();

	ASSERT(cvec->size() == captures.size());
	ASSERT(bodies.size() == 1 && bodies[0].stmts->Tag() == STMT_ZAM);

	captures_vec = std::move(cvec);

	auto n = captures.size();
	for ( auto i = 0U; i < n; ++i )
		{
		auto& c_i = captures[i];
		auto& cv_i = (*captures_vec)[i];

		if ( c_i.IsDeepCopy() )
			{
			auto& t = c_i.Id()->GetType();
			auto new_cv_i = cv_i.ToVal(t)->Clone();
			if ( c_i.IsManaged() )
				ZVal::DeleteManagedType(cv_i);

			cv_i = ZVal(new_cv_i, t);
			}
		}
	}

void ScriptFunc::SetCaptures(Frame* f)
	{
	const auto& captures = type->GetCaptures();
	ASSERT(captures);

	delete captures_frame;
	delete captures_offset_mapping;
	captures_frame = f;
	captures_offset_mapping = new OffsetMap;

	int offset = 0;
	for ( const auto& c : *captures )
		{
		captures_offset_mapping->insert_or_assign(c.Id()->Name(), offset);
		++offset;
		}
	}

void ScriptFunc::AddBody(StmtPtr new_body, const std::vector<IDPtr>& new_inits,
                         size_t new_frame_size, int priority, const std::set<EventGroupPtr>& groups)
	{
	if ( new_frame_size > frame_size )
		frame_size = new_frame_size;

	auto num_args = static_cast<size_t>(GetType()->Params()->NumFields());

	if ( num_args > frame_size )
		frame_size = num_args;

	new_body = AddInits(std::move(new_body), new_inits);

	if ( Flavor() == FUNC_FLAVOR_FUNCTION )
		{
		// For functions, we replace the old body with the new one.
		assert(bodies.size() <= 1);
		bodies.clear();
		}

	Body b;
	b.stmts = new_body;
	b.groups = groups;
	current_body = new_body;
	current_priority = b.priority = priority;

	bodies.push_back(b);
	std::stable_sort(bodies.begin(), bodies.end());
	}

void ScriptFunc::ReplaceBody(const StmtPtr& old_body, StmtPtr new_body)
	{
	bool found_it = false;

	for ( auto body = bodies.begin(); body != bodies.end(); ++body )
		if ( body->stmts.get() == old_body.get() )
			{
			if ( new_body )
				{
				body->stmts = new_body;
				current_priority = body->priority;
				}
			else
				bodies.erase(body);

			found_it = true;
			break;
			}

	current_body = new_body;
	}

bool ScriptFunc::DeserializeCaptures(const broker::vector& data)
	{
	auto result = Frame::Unserialize(data);

	ASSERT(result.first);

	auto& f = result.second;

	if ( bodies[0].stmts->Tag() == STMT_ZAM )
		{
		auto& captures = *type->GetCaptures();
		int n = f->FrameSize();

		ASSERT(captures.size() == static_cast<size_t>(n));

		auto cvec = std::make_unique<std::vector<ZVal>>();

		for ( int i = 0; i < n; ++i )
			{
			auto& f_i = f->GetElement(i);
			cvec->push_back(ZVal(f_i, captures[i].Id()->GetType()));
			}

		CreateCaptures(std::move(cvec));
		}

	else
		SetCaptures(f.release());

	return true;
	}

FuncPtr ScriptFunc::DoClone()
	{
	// ScriptFunc could hold a closure. In this case a clone of it must
	// store a copy of this closure.
	//
	// We don't use make_intrusive<> directly because we're accessing
	// a protected constructor.
	auto other = IntrusivePtr{AdoptRef{}, new ScriptFunc()};

	CopyStateInto(other.get());

	other->frame_size = frame_size;
	other->outer_ids = outer_ids;

	if ( captures_frame )
		{
		other->captures_frame = captures_frame->Clone();
		other->captures_offset_mapping = new OffsetMap;
		*other->captures_offset_mapping = *captures_offset_mapping;
		}

	if ( captures_vec )
		{
		auto cv_i = captures_vec->begin();
		other->captures_vec = std::make_unique<std::vector<ZVal>>();
		for ( auto& c : *type->GetCaptures() )
			{
			// Need to clone cv_i.
			auto& t_i = c.Id()->GetType();
			auto cv_i_val = cv_i->ToVal(t_i)->Clone();
			other->captures_vec->push_back(ZVal(cv_i_val, t_i));
			++cv_i;
			}
		}

	return other;
	}

broker::expected<broker::data> ScriptFunc::SerializeCaptures() const
	{
	if ( captures_vec )
		{
		auto& cv = *captures_vec;
		auto& captures = *type->GetCaptures();
		int n = captures_vec->size();
		auto temp_frame = make_intrusive<Frame>(n, this, nullptr);

		for ( int i = 0; i < n; ++i )
			{
			auto c_i = cv[i].ToVal(captures[i].Id()->GetType());
			temp_frame->SetElement(i, c_i);
			}

		return temp_frame->Serialize();
		}

	if ( captures_frame )
		return captures_frame->Serialize();

	// No captures, return an empty vector.
	return broker::vector{};
	}

void ScriptFunc::Describe(ODesc* d) const
	{
	d->Add(Name());

	d->NL();
	d->AddCount(frame_size);
	for ( const auto& body : bodies )
		{
		body.stmts->AccessStats(d);
		body.stmts->Describe(d);
		}
	}

StmtPtr ScriptFunc::AddInits(StmtPtr body, const std::vector<IDPtr>& inits)
	{
	if ( inits.empty() )
		return body;

	auto stmt_series = make_intrusive<StmtList>();
	stmt_series->Stmts().push_back(make_intrusive<InitStmt>(inits));
	stmt_series->Stmts().push_back(std::move(body));

	return stmt_series;
	}

BuiltinFunc::BuiltinFunc(built_in_func arg_func, const char* arg_name, bool arg_is_pure)
	: Func(BUILTIN_FUNC)
	{
	func = arg_func;
	name = make_full_var_name(GLOBAL_MODULE_NAME, arg_name);
	is_pure = arg_is_pure;

	const auto& id = lookup_ID(Name(), GLOBAL_MODULE_NAME, false);
	if ( ! id )
		reporter->InternalError("built-in function %s missing", Name());
	if ( id->HasVal() )
		reporter->InternalError("built-in function %s multiply defined", Name());

	type = id->GetType<FuncType>();
	id->SetVal(make_intrusive<FuncVal>(IntrusivePtr{NewRef{}, this}));
	id->SetConst();
	}

bool BuiltinFunc::IsPure() const
	{
	return is_pure;
	}

ValPtr BuiltinFunc::Invoke(Args* args, Frame* parent) const
	{
	if ( spm )
		spm->StartInvocation(this);

	SegmentProfiler prof(segment_logger, Name());

	if ( sample_logger )
		sample_logger->FunctionSeen(this);

	auto [handled, hook_result] = PLUGIN_HOOK_WITH_RESULT(
		HOOK_CALL_FUNCTION, HookCallFunction(this, parent, args), empty_hook_result);

	CheckPluginResult(handled, hook_result, FUNC_FLAVOR_FUNCTION);

	if ( handled )
		{
		if ( spm )
			spm->EndInvocation();
		return hook_result;
		}

	if ( g_trace_state.DoTrace() )
		{
		ODesc d;
		DescribeDebug(&d, args);

		g_trace_state.LogTrace("\tBuiltin Function called: %s\n", d.Description());
		}

	const CallExpr* call_expr = parent ? parent->GetCall() : nullptr;
	call_stack.emplace_back(CallInfo{call_expr, this, *args});
	auto result = std::move(func(parent, args).rval);
	call_stack.pop_back();

	if ( result && g_trace_state.DoTrace() )
		{
		ODesc d;
		result->Describe(&d);

		g_trace_state.LogTrace("\tFunction return: %s\n", d.Description());
		}

	if ( spm )
		spm->EndInvocation();

	return result;
	}

void BuiltinFunc::Describe(ODesc* d) const
	{
	d->Add(Name());
	d->AddCount(is_pure);
	}

bool check_built_in_call(BuiltinFunc* f, CallExpr* call)
	{
	if ( f->TheFunc() != BifFunc::fmt_bif )
		return true;

	const ExprPList& args = call->Args()->Exprs();
	if ( args.length() == 0 )
		{
		// Empty calls are allowed, since you can't just
		// use "print;" to get a blank line.
		return true;
		}

	const Expr* fmt_str_arg = args[0];
	if ( fmt_str_arg->GetType()->Tag() != TYPE_STRING )
		{
		call->Error("first argument to util::fmt() needs to be a format string");
		return false;
		}

	auto fmt_str_val = fmt_str_arg->Eval(nullptr);

	if ( fmt_str_val )
		{
		const char* fmt_str = fmt_str_val->AsStringVal()->CheckString();

		int num_fmt = 0;
		while ( *fmt_str )
			{
			if ( *(fmt_str++) != '%' )
				continue;

			if ( ! *fmt_str )
				{
				call->Error("format string ends with bare '%'");
				return false;
				}

			if ( *(fmt_str++) != '%' )
				// Not a "%%" escape.
				++num_fmt;
			}

		if ( args.length() != num_fmt + 1 )
			{
			call->Error(
				"mismatch between format string to util::fmt() and number of arguments passed");
			return false;
			}
		}

	return true;
	}

// Gets a function's priority from its Scope's attributes. Errors if it sees any
// problems.
static int get_func_priority(const std::vector<AttrPtr>& attrs)
	{
	int priority = 0;

	for ( const auto& a : attrs )
		{
		if ( a->Tag() == ATTR_DEPRECATED || a->Tag() == ATTR_IS_USED || a->Tag() == ATTR_GROUP )
			continue;

		if ( a->Tag() != ATTR_PRIORITY )
			{
			a->Error("illegal attribute for function body");
			continue;
			}

		auto v = a->GetExpr()->Eval(nullptr);

		if ( ! v )
			{
			a->Error("cannot evaluate attribute expression");
			continue;
			}

		if ( ! IsIntegral(v->GetType()->Tag()) )
			{
			a->Error("expression is not of integral type");
			continue;
			}

		priority = v->InternalInt();
		}

	return priority;
	}

// Get a function's groups from its Scope's attributes. Errors if it sees any
// problems with the group tag.  get_func_priority() checks for illegal
// attributes, so we don't do this here.
static std::set<EventGroupPtr> get_func_groups(const std::vector<AttrPtr>& attrs)
	{
	std::set<EventGroupPtr> groups;

	for ( const auto& a : attrs )
		{
		if ( a->Tag() != ATTR_GROUP )
			continue;

		auto v = a->GetExpr()->Eval(nullptr);

		if ( ! v )
			{
			a->Error("cannot evaluate attribute expression");
			continue;
			}

		if ( ! IsString(v->GetType()->Tag()) )
			{
			a->Error("expression is not of string type");
			continue;
			}

		auto group = event_registry->RegisterGroup(EventGroupKind::Attribute,
		                                           v->AsStringVal()->ToStdStringView());
		groups.insert(group);
		}

	return groups;
	}

FunctionIngredients::FunctionIngredients(ScopePtr _scope, StmtPtr _body,
                                         const std::string& module_name)
	{
	scope = std::move(_scope);
	body = std::move(_body);

	frame_size = scope->Length();
	inits = scope->GetInits();

	id = scope->GetID();

	const auto& attrs = scope->Attrs();

	if ( attrs )
		{
		priority = get_func_priority(*attrs);

		groups = get_func_groups(*attrs);

		for ( const auto& a : *attrs )
			if ( a->Tag() == ATTR_IS_USED )
				{
				// Associate this with the identifier, too.
				id->AddAttr(make_intrusive<Attr>(ATTR_IS_USED));
				break;
				}
		}
	else
		priority = 0;

	// Implicit module event groups for events and hooks.
	auto flavor = id->GetType<zeek::FuncType>()->Flavor();
	if ( flavor == FUNC_FLAVOR_EVENT || flavor == FUNC_FLAVOR_HOOK )
		{
		auto module_group = event_registry->RegisterGroup(EventGroupKind::Module, module_name);
		groups.insert(module_group);
		}
	}

zeek::RecordValPtr make_backtrace_element(std::string_view name, const VectorValPtr args,
                                          const zeek::detail::Location* loc)
	{
	static auto elem_type = id::find_type<RecordType>("BacktraceElement");
	static auto function_name_idx = elem_type->FieldOffset("function_name");
	static auto function_args_idx = elem_type->FieldOffset("function_args");
	static auto file_location_idx = elem_type->FieldOffset("file_location");
	static auto line_location_idx = elem_type->FieldOffset("line_location");

	auto elem = make_intrusive<RecordVal>(elem_type);
	elem->Assign(function_name_idx, name.data());
	elem->Assign(function_args_idx, std::move(args));

	if ( loc )
		{
		elem->Assign(file_location_idx, loc->filename);
		elem->Assign(line_location_idx, loc->first_line);
		}

	return elem;
	}

zeek::VectorValPtr get_current_script_backtrace()
	{
	static auto backtrace_type = id::find_type<VectorType>("Backtrace");

	auto rval = make_intrusive<VectorVal>(backtrace_type);

	// The body of the following loop can wind up adding items to
	// the call stack (because MakeCallArgumentVector() evaluates
	// default arguments, which can in turn involve calls to script
	// functions), so we work from a copy of the current call stack
	// to prevent problems with iterator invalidation.
	auto cs_copy = zeek::detail::call_stack;

	for ( auto it = cs_copy.rbegin(); it != cs_copy.rend(); ++it )
		{
		const auto& ci = *it;
		if ( ! ci.func )
			// This happens for compiled code.
			continue;

		const auto& params = ci.func->GetType()->Params();
		auto args = MakeCallArgumentVector(ci.args, params);

		auto elem = make_backtrace_element(ci.func->Name(), std::move(args),
		                                   ci.call ? ci.call->GetLocationInfo() : nullptr);
		rval->Append(std::move(elem));
		}

	return rval;
	}

static void emit_builtin_error_common(const char* msg, Obj* arg, bool unwind)
	{
	auto emit = [=](const CallExpr* ce)
	{
		if ( ce )
			{
			if ( unwind )
				{
				if ( arg )
					{
					ODesc d;
					arg->Describe(&d);
					reporter->ExprRuntimeError(ce, "%s (%s), during call:", msg, d.Description());
					}
				else
					reporter->ExprRuntimeError(ce, "%s", msg);
				}
			else
				ce->Error(msg, arg);
			}
		else
			{
			if ( arg )
				{
				if ( unwind )
					reporter->RuntimeError(arg->GetLocationInfo(), "%s", msg);
				else
					arg->Error(msg);
				}
			else
				{
				if ( unwind )
					reporter->RuntimeError(nullptr, "%s", msg);
				else
					reporter->Error("%s", msg);
				}
			}
	};

	if ( call_stack.empty() )
		{
		// Shouldn't happen unless someone (mistakenly) calls builtin_error()
		// from somewhere that's not even evaluating script-code.
		emit(nullptr);
		return;
		}

	auto last_call = call_stack.back();

	if ( call_stack.size() < 2 )
		{
		// Don't need to check for wrapper function like "<module>::__<func>"
		emit(last_call.call);
		return;
		}

	auto starts_with_double_underscore = [](const std::string& name) -> bool
	{
		return name.size() > 2 && name[0] == '_' && name[1] == '_';
	};
	std::string last_func = last_call.func->Name();

	auto pos = last_func.find_first_of("::");
	std::string wrapper_func;

	if ( pos == std::string::npos )
		{
		if ( ! starts_with_double_underscore(last_func) )
			{
			emit(last_call.call);
			return;
			}

		wrapper_func = last_func.substr(2);
		}
	else
		{
		auto module_name = last_func.substr(0, pos);
		auto func_name = last_func.substr(pos + 2);

		if ( ! starts_with_double_underscore(func_name) )
			{
			emit(last_call.call);
			return;
			}

		wrapper_func = module_name + "::" + func_name.substr(2);
		}

	auto parent_call = call_stack[call_stack.size() - 2];
	auto parent_func = parent_call.func->Name();

	if ( wrapper_func == parent_func )
		emit(parent_call.call);
	else
		emit(last_call.call);
	}

void emit_builtin_exception(const char* msg)
	{
	emit_builtin_error_common(msg, nullptr, true);
	}

void emit_builtin_exception(const char* msg, const ValPtr& arg)
	{
	emit_builtin_error_common(msg, arg.get(), true);
	}

void emit_builtin_exception(const char* msg, Obj* arg)
	{
	emit_builtin_error_common(msg, arg, true);
	}

void init_primary_bifs()
	{
	if ( did_builtin_init )
		return;

	ProcStats = id::find_type<RecordType>("ProcStats");
	NetStats = id::find_type<RecordType>("NetStats");
	MatcherStats = id::find_type<RecordType>("MatcherStats");
	ConnStats = id::find_type<RecordType>("ConnStats");
	ReassemblerStats = id::find_type<RecordType>("ReassemblerStats");
	DNSStats = id::find_type<RecordType>("DNSStats");
	GapStats = id::find_type<RecordType>("GapStats");
	EventStats = id::find_type<RecordType>("EventStats");
	TimerStats = id::find_type<RecordType>("TimerStats");
	FileAnalysisStats = id::find_type<RecordType>("FileAnalysisStats");
	ThreadStats = id::find_type<RecordType>("ThreadStats");
	BrokerStats = id::find_type<RecordType>("BrokerStats");
	ReporterStats = id::find_type<RecordType>("ReporterStats");

	var_sizes = id::find_type("var_sizes")->AsTableType();

#include "CPP-load.bif.func_init"
#include "communityid.bif.func_init"
#include "option.bif.func_init"
#include "packet_analysis.bif.func_init"
#include "reporter.bif.func_init"
#include "stats.bif.func_init"
#include "strings.bif.func_init"
#include "supervisor.bif.func_init"
#include "zeek.bif.func_init"

	init_builtin_types();
	did_builtin_init = true;
	}

	} // namespace detail

void emit_builtin_error(const char* msg)
	{
	zeek::detail::emit_builtin_error_common(msg, nullptr, false);
	}

void emit_builtin_error(const char* msg, const zeek::ValPtr& arg)
	{
	zeek::detail::emit_builtin_error_common(msg, arg.get(), false);
	}

void emit_builtin_error(const char* msg, Obj* arg)
	{
	zeek::detail::emit_builtin_error_common(msg, arg, false);
	}

	} // namespace zeek
